package csmacd;

import java.util.PriorityQueue;
import java.util.Comparator;
import java.util.Vector;
import java.util.Random;

import csmacd.SimEvent;

enum status {BUSY, IDLE};
enum event_type {MAC_ARRIVAL, TRANSMISSION_START, TRANSMISSION_END, SET_BUSY}; //type of event

public class SimulationLAN {
	
	public final int term_sim = 12000000;	//simulation termination time
	public int A;						//Poisson distribution mean (in packets/sec)
	public int W;						//the speed of the LAN
	public int N;						//number of computers in the LAN
	public int L;						//Length of the packet (kiloBytes)
	
	public status LAN_status;			//status of the LAN
	public int clock;    				//clock variable (counts in milliseconds)
	public boolean persistent;			//persistent carrier sensing?
	
	public int packets_generated;
	public int packets_sent;			//the total number of packets that have been sent successfully
	public int packets_retried;			//the total number of packets that had to be retransmitted due to collision
	
	public int cumulative_waiting_time;	//total amount of time of delay across all packets and computers
	public int RETRY_PERSISTENT_DURATION = 20;		//amount of delay between successive attempts
													//in the persistent mode, less than half of optimal delay
	
	public PriorityQueue<SimEvent> event_list; //the event list
	public Vector<SimEvent> transmission_events; //copy of events in transmission phase
	
	public Random generator; //random number generator
	
	public static void main(String[] args) {
		SimulationLAN sim = new SimulationLAN(args);
		sim.start();
	}
	
	private class MyComparator implements Comparator<SimEvent> {
		
		public int compare(SimEvent e, SimEvent f) {
			if(e.t_arrival < f.t_arrival) {
				return -1;
			}
			else if(e.t_arrival == f.t_arrival) {
				return 0;
			}
			else 
				return 1;	
		}
	}
	
	public SimulationLAN(String[] args) {
//		startup syntax:  java SimulationLAN [VARY_N|VARY_A] [N|A] [PERSISTENT|NON_PERSISTENT]
		clock = 0;
		LAN_status= status.IDLE;
		
		if(args[0].equalsIgnoreCase("VARY_N")) {
			N = Integer.parseInt(args[1]);
		} else {
			N = 10;
		}
		
		if(args[0].equalsIgnoreCase("VARY_A")) {
			A = Integer.parseInt(args[1]);
		} else {
			A = 20;
		}
		
		if(args[2].equalsIgnoreCase("PERSISTENT")) {
			persistent = true;
		} else if(args[2].equalsIgnoreCase("NON-PERSISTENT")) {
			persistent = false;
		}
		
		W = 1500;			//1500 kBytes/sec
		L = 64;				//64 kByte packet length
		
		packets_generated = 0;
		packets_sent = 0;
		packets_retried = 0;
		cumulative_waiting_time = 0;

		//L / W must be <= 2 * L
		
		event_list = new PriorityQueue<SimEvent>(2 * N, new MyComparator());
		transmission_events = new Vector<SimEvent>();
	
		generator = new Random();
	}
	
	public void start() {
		for(int i = 0; i < N; i++){
			SimEvent e = new SimEvent(i);
			e.t_arrival = clock + getNewArrivalTime(); //schedule events
			//since arrival times can't overlap with a TreeMap
			event_list.add(e);
			packets_generated += 1;
		}
		
		while (clock < term_sim) {
			SimEvent e = event_list.poll();
			if (e == null) {
				System.err.println("NULL");
				break;
			}
			clock = e.t_arrival;
			if(e.type == event_type.MAC_ARRIVAL) {
				arrival(e);
			} else if (e.type == event_type.TRANSMISSION_END) {
				departure(e);
			} else if (e.type == event_type.SET_BUSY) {
				if (LAN_status == status.BUSY) {
					jam_transmissions();
				} else {
					//ensure this set_busy event isn't obsolete from before a jam
					if (transmission_events.size() > 0) {
						LAN_status = status.BUSY;
					}
					//System.out.println("SET BUSY " + e.N);
				}
			}
		}
		show_results();
	}
	
	//return a new Arrival time
	public int getNewArrivalTime() {
		//interarrival time in milliseconds
		double second = 1000.0;
		double i = -1.0 * ((1.0 / (double) A) * second) * Math.log(generator.nextDouble());
		//floor for next arrival time is 1
		if (i < 1.0) {
			return 1;
		}
		return (int) i;
	}
	
	//simulate an arrival, use sensing
	public void arrival(SimEvent e) {
		////System.out.println("ARRIVAL");
		if (LAN_status == status.IDLE) {
				e.type = event_type.TRANSMISSION_START;
				start_service(e);
		} else {
			if (persistent) {
				e.addWait(RETRY_PERSISTENT_DURATION);
				e.t_arrival = clock + RETRY_PERSISTENT_DURATION;
				event_list.add(e);
			} else if (!persistent) {
				//backoff algorithm based on class notes
				double wait = generator.nextDouble() * (1000.0 * Math.pow(2, e.i)) + 1.0;
				e.addWait((int) wait);
				e.t_arrival = clock + (int) wait;
				e.i++;
				event_list.add(e);
			}
			//System.out.println("Busy " + e.N + " at clock " + clock + " with arrival time of " + e.t_arrival + ".");
		}
	}
	
	//start services
	public void start_service(SimEvent e) {
		////System.out.println("START SERVICE");
		if (transmission_events.size() > 0) {
			//System.out.println("JAMMER: " + e.N);
			jam_transmissions();
			//add the jamming event (converted to arrival) back to event_list
			//reschedule arrival using random number from 1 to 1000 ms
			e.type = event_type.MAC_ARRIVAL;
			double wait = generator.nextDouble() * 1000.0 + 1.0;
			////System.out.println(wait);
			e.t_arrival = clock + (int) wait;
			e.addWait((int) wait);
			event_list.add(e);			
		} else {
			//System.out.println("CLAIMER: " + e.N + " at " + clock);
			//add an event for others in LAN to detect busy MAC soon
			double propagate_time = 2;
			//LAN_status = status.BUSY;
			SimEvent se = new SimEvent(e.N);
			se.t_arrival = clock + (int) propagate_time;
			se.type = event_type.SET_BUSY;
			event_list.add(se);
			//busy_events.add(se);
			
			//schedule departure
			// (L/W)*1000 is equal to the transmission time in milliseconds
			double transmit_time = ((double) L / (double) W) * (double) 1000.0;
			e.t_arrival = clock + (int) transmit_time; 
												
			e.type = event_type.TRANSMISSION_END;
			
			transmission_events.add(e);
			event_list.add(e);
		}
	}
	
	//based on speed, it is assumed jam signal can be sent in less than 1 millisecond
	public void jam_transmissions() {
		//Collection<SimEvent> transmissions = transmission_events.values();
		//transmission_events.clear();
		for (SimEvent e : transmission_events) {
			packets_retried += 1;
			////System.out.println("RETRY: " + e.N + ", " + clock);
			//remove event from main event_list
			event_list.remove(e);
			//add the time spent wasted trying to transmit to wait time
			double transmission_time = (double) L / (double) W;
			transmission_time *= 1000.0;
			double timeWasted = (double) clock - ((double) e.t_arrival - transmission_time);
			e.addWait((int) timeWasted);
			//reschedule arrival using random number from 1 to 1000 ms
			e.type = event_type.MAC_ARRIVAL;
			double wait = (generator.nextDouble() * 1000.0 + 1.0);
			////System.out.println(wait);
			e.t_arrival = clock + (int) wait;
			e.addWait((int) wait);
			event_list.add(e);
		}
		transmission_events.clear();
		Vector<SimEvent> busy = new Vector<SimEvent>();
		for (SimEvent b : event_list) {
			if (b.type != event_type.MAC_ARRIVAL) {
				busy.add(b);
				//event_list.remove(b);
			}
		}
		for (SimEvent c : busy) {
			event_list.remove(c);
			//System.out.println("REMOVE B: " + c.N);
		}
//			busy_events.clear();
		LAN_status = status.IDLE;
	}
	
	//depart from any stage
	public void departure(SimEvent e) {
		////System.out.println("DEPARTURE");
		packets_sent += 1;
		double transmission_time = (double) L / (double) W;
		transmission_time *= 1000.0;
		cumulative_waiting_time += (int) transmission_time + e.getWait();
		transmission_events.remove(e);
		//System.out.println("PROCESSED: " + e.N);
		LAN_status = status.IDLE;
			
		SimEvent eNew = new SimEvent(e.N);	//create a new arrival event
		eNew.t_arrival = clock + getNewArrivalTime();	//schedule it
		eNew.i = e.i;
		packets_generated += 1;
		event_list.add(eNew);	//add it to the list
	}
	
	public void show_results() {
		System.out.println("Input Values:");
		System.out.println("N: " + N + " computers");
		System.out.println("A: " + A + " packets/second.");
		System.out.println("W: " + W + " kilobytes/second.");
		System.out.println("L: " + L + " kilobytes/packet.");
		if (persistent) {
			System.out.println("1-Persistent carrier sensing.");
		} else {
			System.out.println("Non-persistent carrier sensing.");
		}
		System.out.println("Results:");
		System.out.println("Test duration: " + (term_sim / 1000) + " seconds");
		System.out.println("Packets generated: " + packets_generated);
		System.out.println("Packets sent: " + packets_sent);
		double throughput = ((double) packets_sent * (double) L) / ((double) term_sim / (double) 1000.0);
		System.out.println("Optimal throughput: " + (double) W + " kilobytes per second");
		System.out.println("Actual throughput: " + throughput + " kilobytes per second");
		double delay = (double) cumulative_waiting_time / (double) packets_sent;
		double wait = ((double) L / (double) W) * (double) 1000.0;
		System.out.println("Optimal delay: " + wait + " milliseconds per packet");
		System.out.println("Actual delay: " + delay + " milliseconds per packet");
		System.out.println("Transmission retries due to collision: " + packets_retried);
	}
}
